#include <pic.h>
#include "display.h"
#include "delay.h"
#include "defines.h"

__CONFIG (0x2F4A);
/*
 * TIMER1 @ 20MHz/4 *1/8 (prescaler) = 625kHz
 * 16 Bit resolution: 0,0000016 s
 * => overflow every 0,1048576 s
 * Only using the high 8 bits gives a resolution of 0,0004096s
 * For distance and time measurements the exact values are used
 * 
                       RC7 |- RS232RX (unused)
                       RC6 |- RS232TX (unused)
                       RC5 |- Adress Enable
                       RC4 |- SDA
                       RC3 |- SCL
                       RC2 |- CCP1
                       RC1 |- CCP2
                       RC0 |- Adressbit 1

                       RB7 |- Databus
                       RB6 |- Databus
 unused     -| RA5     RB5 |- Databus    
 LCDRS      -| RA4     RB4 |- Databus
 Adressbit 3-| RA3     RB3 |- Databus
 Adressbit 2-| RA2     RB2 |- Databus
 unused     -| RA1     RB1 |- Databus
 analog in  -| RA0     RB0 |- Databus

 */

/* Variables used for distance and time measurement 
 * Time: in h/100: 36s resolution, max 42 949 672,95 h
 *		in h/1600: 2,25s res., max 2 684 354,56 h
 * Distance: in 10m: 10m resolution, max 42 949 672,95 km
 * 			 in 10/16m: 10/16m res., max 2 684 354,56 km
 */
// Stored variables
unsigned long time1;		// in h/100
unsigned long time2;		// in h/100
unsigned long distance1; 	// in 10m
unsigned long distance2; 	// in 10m
unsigned int avgSpeed1; 	// in 10m/h
unsigned int avgSpeed2; 	// in 10m/h
unsigned long tickCounter1;	// in TMR1 ticks int: max ca 160 km
unsigned long tickCounter2;	// in TMR1 ticks

/* Changeable */
unsigned int wheelCircumference; // in mm
unsigned char weight;

/* used for speed calculations */
unsigned int t0;
unsigned char oldtime;
unsigned bank1 int delta_T;

/* Power calculations */
unsigned int power;		// in 1/100 W
unsigned long energy;	// in 1/10 MJ
signed bank1 int sinAngle;
unsigned bank1 int oldSpeed;

/* When bigger then 22500000 (h/1600:1406250), time1 and 2 get updatetd */
unsigned bank1 long timecounter;
unsigned bank1 int distancecounter; //When bigger then 10000, add 1 to distance
unsigned bank1 long energyCounter;  // when bigger than ENERGY_FACTOR add 1 to energy

/* How often did the timer 1 overflow? */
unsigned bank1 char tm1OverflowCounter;
unsigned char sleepCounter;

/* Internal state */
unsigned bank1 char state; // What schould be shown?
unsigned int speed; // in 10m/h 
unsigned bank1 char resetCounter;
unsigned bank1 char buttons;
unsigned bank1 char buttonsOld;

/* calculation simplifications */
unsigned bank1 long wheelTimesFactor;

/* Switches */
bit calculateUpdate;
bit sleepNow;
bit updateScreen;
bit buttonPressed;
bit updateData;
bit setSpeedToZero;
bit updatePower;

/* Read one byte from the internal EEPROM */
char ReadEEPROMData(char addr) {
	EEADR = addr;
	RD = 1;
	while(RD) ;
	return EEDATA;
}

/* Write one byte to the internal EEPROM */
void WriteEEPROMData(char addr, char data) {
	while(WR) ;
	EEADR = addr;
	EEDATA = data;
	GIE =0;
	// Required Sequence
    EECON2 = 0x55;
    EECON2 = 0xAA;
    WR = 1;
	GIE =1;
}

/* Write all important Data to the EEPROM */
void SaveValues() {
	WriteEEPROMData(time1HH, byte3(time1));
	WriteEEPROMData(time1HL, byte2(time1));
	WriteEEPROMData(time1LH, byte1(time1));
	WriteEEPROMData(time1LL, byte0(time1));

	WriteEEPROMData(time2HH, byte3(time2));
	WriteEEPROMData(time2HL, byte2(time2));
	WriteEEPROMData(time2LH, byte1(time2));
	WriteEEPROMData(time2LL, byte0(time2));

	WriteEEPROMData(distance1HH, byte3(distance1));
	WriteEEPROMData(distance1HL, byte2(distance1));
	WriteEEPROMData(distance1LH, byte1(distance1));
	WriteEEPROMData(distance1LL, byte0(distance1));

	WriteEEPROMData(distance2HH, byte3(distance2));
	WriteEEPROMData(distance2HL, byte2(distance2));
	WriteEEPROMData(distance2LH, byte1(distance2));
	WriteEEPROMData(distance2LL, byte0(distance2));

	WriteEEPROMData(avgSpeed1H, hibyte(avgSpeed1));
	WriteEEPROMData(avgSpeed1L, lobyte(avgSpeed1));

	WriteEEPROMData(avgSpeed2H, hibyte(avgSpeed2));
	WriteEEPROMData(avgSpeed2L, lobyte(avgSpeed2));

	WriteEEPROMData(tickCounter1HH, byte3(tickCounter1));
	WriteEEPROMData(tickCounter1HL, byte2(tickCounter1));
	WriteEEPROMData(tickCounter1LH, byte1(tickCounter1));
	WriteEEPROMData(tickCounter1LL, byte0(tickCounter1));

	WriteEEPROMData(tickCounter2HH, byte3(tickCounter2));
	WriteEEPROMData(tickCounter2HL, byte2(tickCounter2));
	WriteEEPROMData(tickCounter2LH, byte1(tickCounter2));
	WriteEEPROMData(tickCounter2LL, byte0(tickCounter2));

	WriteEEPROMData(wheelCircumferenceH, hibyte(wheelCircumference));
	WriteEEPROMData(wheelCircumferenceL, lobyte(wheelCircumference));

	WriteEEPROMData(weight,weightL);

	WriteEEPROMData(energyHH, byte3(energy));
	WriteEEPROMData(energyHL, byte2(energy));
	WriteEEPROMData(energyLH, byte1(energy));
	WriteEEPROMData(energyLL, byte0(energy));
}

/* Load all important Data from the EEPROM */
void LoadValues() {
	time1 = (ReadEEPROMData(time1HH)<<8)+ReadEEPROMData(time1HL);
	time1<<=8;
	time1<<=8;
	time1 += (ReadEEPROMData(time1LH)<<8)+ ReadEEPROMData(time1LL);

	time2 = (ReadEEPROMData(time2HH)<<8)+ReadEEPROMData(time2HL);
	time2<<=8;
	time2<<=8;
	time2 += (ReadEEPROMData(time2LH)<<8)+ ReadEEPROMData(time2LL);

	distance1 = (ReadEEPROMData(distance1HH)<<8)+ReadEEPROMData(distance1HL);
	distance1<<=8;
	distance1<<=8;
	distance1 += (ReadEEPROMData(distance1LH)<<8)+ ReadEEPROMData(distance1LL);

	distance2 = (ReadEEPROMData(distance2HH)<<8)+ReadEEPROMData(distance2HL);
	distance2<<=8;
	distance2<<=8;
	distance2 += (ReadEEPROMData(distance2LH)<<8)+ ReadEEPROMData(distance2LL);

	avgSpeed1 = (ReadEEPROMData(avgSpeed1H)<<8)+ReadEEPROMData(avgSpeed1L);
	avgSpeed2 = (ReadEEPROMData(avgSpeed2H)<<8)+ReadEEPROMData(avgSpeed2L);

	tickCounter1 = (ReadEEPROMData(tickCounter1HH)<<8)+ReadEEPROMData(tickCounter1HL);
	tickCounter1<<=8;
	tickCounter1<<=8;
	tickCounter1 += (ReadEEPROMData(tickCounter1LH)<<8)+ ReadEEPROMData(tickCounter1LL);

	tickCounter2 = (ReadEEPROMData(tickCounter2HH)<<8)+ReadEEPROMData(tickCounter2HL);
	tickCounter2<<=8;
	tickCounter2<<=8;
	tickCounter2 += (ReadEEPROMData(tickCounter2LH)<<8)+ ReadEEPROMData(tickCounter2LL);

	wheelCircumference = (ReadEEPROMData(wheelCircumferenceH)<<8)+ReadEEPROMData(wheelCircumferenceL);
	
	weight = ReadEEPROMData(weightL);

	energy = (ReadEEPROMData(energyHH)<<8)+ReadEEPROMData(energyHL);
	energy<<=8;
	energy<<=8;
	energy += (ReadEEPROMData(energyLH)<<8)+ ReadEEPROMData(energyLL);

}

void Init() {
	/* Init In-/Outputs */
	TRISB = ALL_OUTPUT; //Port B is output
	PORTB = 0;
	TRISA = 0x01; // old: 0x23; // AN0 analog input RA1 RA5 buttons 0010 0011
	TRISC = 0x04; // RC2/CCP1 set as input (was 0x06)
	//PORTC = 0;

	/*Init LCD Display */
	LCDInit();
	
	/* Setup Writeing/reading from EEPROM */
	EEPGD = 0;   // 0000
	WREN = 1; //Allow Writing
	
	LoadValues();
	/* Init AD Converter
	 * Set the conversion to Clock/32 for 20MHz and 10 bit (right) output, input is AN0
	 */
	ADCON0 = 0x81;	//10 000 1 0 1
	ADCON1 = 0xCE;	// 1 1 00 1110

	/* Setting up the TIMER1 and the CCP1 to Compare */
	/*
	 * 16-Bit Timer (overflow 65536)
	 */
	T1CON	= 0x31; // 00 11 (1:8 pre)
	CCP1CON = 0x05; // capture every rising edge (every: 0x05, 4th edge: 0x06, 16th edge:0x07)

	/* Clearing interrupt flags */
	CCP1IF = 0;
	TMR1IF = 0;

	/* Setting upt the interrupts for TIMER 1 and CCP1 */
	// GIE = 1; PEIE = 1;
	INTCON = 0xC0; // 1100 0000
    // CCP1IE = 1; TMR1IE = 1;
	PIE1 = 0x05; //0000 0101

	/* Initialize values */
	state = 0;
	speed = 0;
	oldSpeed = 0;
	power = 0;
	buttonsOld = 0x00;
	
	
	updateData = TRUE;
	updateScreen = TRUE;
	setSpeedToZero = FALSE;
	updatePower = FALSE;

	/* Insert Wheel circumference here (in mm) */
	if(wheelCircumference==0) {
		wheelCircumference= 2500;
	}
	if(weight==0) {
		weight= 100;
	}

	wheelTimesFactor = (long)wheelCircumference* KPH_CORRECTION;
	
	/* Write Initial states to the display */
	WriteKPH();
	WriteSpeed(speed);
}

void main(void) {
	unsigned int tmpWheelCircumference;
	unsigned char tmpWeight;
	unsigned char settingsState;
	unsigned char flanks;
	signed long tmp;

	settingsState = 0;

	Init();	

	/* Main loop */
	while(1) {

		ADGO = 1; // Start A/D conversation		

		if(sleepNow) {
			sleepNow = 0;
			sleepNow=FALSE;
			WriteSleep();
			SaveValues();	//write data
			//stop TIMER1/Interrupts
			gie_off;
			TMR1IE = 0;
			TMR1ON = 0;
			// stop display
			SwitchOffDisplay();
			// stop ADConversion
			ADON = 0;			
			SLEEP();
			Init();
		}

		/* Get Pushbuttons */
		DATABUS_DIR = ALL_INPUT;
		selectButton(); //set Pushbutton Interface Adress
		AdrEnable = TRUE;
		buttons = DATABUS;
		AdrEnable = FALSE;
		DATABUS_DIR = ALL_OUTPUT;
		
		/* detect positive flanks */
		flanks = buttons^buttonsOld; //detect all changed bits
		flanks &= buttonsOld; // remove all negative flanks
		buttonsOld = buttons;

		/* Don't sleep when buttons are pressed */
		if(flanks) {
			sleepCounter =0;
		}

		/* State change on positive flank */
		if (flanks&BUTTON_NEXT) {
			state++;
			if(state > 9) {
				state = 0;
			}
			updateScreen = TRUE;
			updateData = TRUE;
		}
		
		/* State actions */
		switch (state) {
			case 0: // Show default: distance 1
				if (updateData) {
					leaveSettings();
					WriteDistTime(distance1);
				}
				if (updateScreen) {
					WriteSet1('D');
				}
			break;
			case 1: // Show time 1
				if (updateScreen) {
					WriteSet1('T');
				}
				if (updateData) {
					WriteDistTime(time1);
				}
			break;
			case 2: // Show av.speed 1
				if (updateScreen) {
					WriteSet1('V');
				}
				if (updateData) {
					WriteAvgSpeed(avgSpeed1);
				}
			break;
			case 3: // Show distance 2
				if (updateData) {
					WriteDistTime(distance2);
				}
				if (updateScreen) {
					WriteSet2('D');
				}
			break;
			case 4:  // Show time 2
				if (updateScreen) {
					WriteSet2('T');
				}
				if (updateData) {
					WriteDistTime(time2);
				}
			break;
			case 5: // Show av.speed 2
				if (updateScreen) {
					WriteSet2('V');
				}
				if (updateData) {
					WriteAvgSpeed(avgSpeed2);
				}
			break;
			case 6: // Show Power
				if (updateScreen) {
					WritePower();
				}
				if (updateData) {
					WriteAvgSpeed(power);
				}
			break;
			case 7: // Show Energy
				if (updateScreen) {
					WriteEnergy();
				}
				if (updateData) {
					WriteDistTime(energy);
				}
			break;
			case 8: // Settings Wheelcircumference
				if(updateScreen) {
					WriteSettings();
					enterSettings();
					settingsState = 0;
					tmpWheelCircumference = wheelCircumference;
					updateSettingsData(tmpWheelCircumference);
					setSettingsCursor(settingsState);
				}

				if(flanks&BUTTON_RIGHT) {
					if(settingsState==0) {
						settingsState=3;
					} else {
						settingsState--;
					}
					setSettingsCursor(settingsState);
				}	
				if(flanks&BUTTON_LEFT) {
					if(settingsState==3) {
						settingsState=0;
					} else {
						settingsState++;
					}
					setSettingsCursor(settingsState);
				}	
				if(flanks&BUTTON_UP) {
					if(settingsState == 0) {
						tmpWheelCircumference += 1;
					} else if (settingsState == 1) {
						tmpWheelCircumference += 10;
					} else if (settingsState == 2) {
						tmpWheelCircumference += 100;
					} else if (settingsState == 3) {
						tmpWheelCircumference += 1000;
					}
					updateSettingsData(tmpWheelCircumference);
					setSettingsCursor(settingsState);
				}	
				if(flanks&BUTTON_DOWN) {
					if(settingsState == 0) {
						tmpWheelCircumference -= 1;
					} else if (settingsState == 1) {
						tmpWheelCircumference -= 10;
					} else if (settingsState == 2) {
						tmpWheelCircumference -= 100;
					} else if (settingsState == 3) {
						tmpWheelCircumference -= 1000;
					}
					updateSettingsData(tmpWheelCircumference);
					setSettingsCursor(settingsState);
				}				
				if(flanks&BUTTON_SET) {
					wheelCircumference = tmpWheelCircumference;
					settingsState = 0;
					wheelTimesFactor = (long)wheelCircumference* KPH_CORRECTION;
				}
			break;
			case 9: // Settings Weight
				if(updateScreen) {
					WriteSettings2();
					enterSettings();
					settingsState = 0;
					tmpWeight = weight;
					updateSettingsData2(tmpWeight);
					setSettingsCursor(settingsState);
				}

				if(flanks&BUTTON_RIGHT) {
					if(settingsState==0) {
						settingsState=2;
					} else {
						settingsState--;
					}
					setSettingsCursor2(settingsState);
				}	
				if(flanks&BUTTON_LEFT) {
					if(settingsState==2) {
						settingsState=0;
					} else {
						settingsState++;
					}
					setSettingsCursor2(settingsState);
				}	
				if(flanks&BUTTON_UP) {
					if(settingsState == 0) {
						tmpWeight += 1;
					} else if (settingsState == 1) {
						tmpWeight += 10;
					} else if (settingsState == 2) {
						if (tmpWeight < 200) {
							tmpWeight += 100;
						}
					}
					updateSettingsData2(tmpWeight);
					setSettingsCursor2(settingsState);
				}	
				if(flanks&BUTTON_DOWN) {
					if(settingsState == 0) {
						tmpWeight -= 1;
					} else if (settingsState == 1) {
						if (tmpWeight > 10) {
							tmpWeight -= 10;
						}
					} else if (settingsState == 2) {
						if (tmpWeight > 100) {
							tmpWeight -= 100;
						}
					}
					updateSettingsData2(tmpWeight);
					setSettingsCursor2(settingsState);
				}				
				if(flanks&BUTTON_SET) {
					weight = tmpWeight;
					settingsState = 0;
				}
			break;
			default: // Show default: distance 1
				state = 0;
			break;
		}

		/* If reset button pressed! */
		if((flanks&BUTTON_RESET) && (state!=8)&& (state!=9)) {	
			WriteReset();
		//	resetCounter++;
		//	if (resetCounter ==0) {
				if(state==0) {
					distance1 = 0;
				} else if (state==1){
					time1 = 0; 
				} else if (state==2){
					avgSpeed1 = 0;
					tickCounter1 = 0;
				} else if(state == 3) {
					distance2 = 0;
				} else if(state == 4) {
					time2 = 0;
				} else if(state == 5) {
					avgSpeed2 = 0;
					tickCounter2 = 0;
				} else if(state == 7) {
					energy = 0;
					energyCounter = 0;
				}
				updateData = TRUE;
		//	}
		} // else {
		//	resetCounter =0;
		//}

		/* Calculate power */
		if (updatePower) {
			while(ADGO) ;
			// Raw Data: -90: 458; 0: 520; +90: 585
			// Resultnew : -90: -65; 0: 0; +90: 65
			sinAngle = (char)((((ADRESH<<8)+ADRESL-457))-64);

			tmp = (long)(speed-oldSpeed)*ACCEL_FACTOR/delta_T+(long)GRAVITY_FACTOR*sinAngle;
			tmp *=weight;
			tmp += (long)speed*speed/AIR_RESISTANCE;
			tmp /= 10; // Additional conversion factor;
			tmp *= speed/OVERALL_FACTOR;
			if (tmp > 0) {
				power = tmp;
			} else {
				power = 0;
			}
			energyCounter += power * delta_T;
			while (energyCounter > ENERGY_FACTOR) {
				energy ++;
				energyCounter -= ENERGY_FACTOR;
			}
		}

		updateScreen = FALSE;
		updateData = FALSE;

		if (setSpeedToZero) {
			if(speed) {
				oldSpeed = speed;
				speed = 0;
				WriteSpeed(speed);
				oldtime = 0;
				power = 0;
				updateData = TRUE;
			}
			setSpeedToZero = FALSE;
		}

		/* Do all the Calculations if needed */
		if(calculateUpdate) {	
			calculateUpdate = 0;
			oldSpeed = speed;
			delta_T	= t0-oldtime;
			speed = (wheelTimesFactor)/delta_T;
			WriteSpeed(speed);
			
			/* Calculate average speed */
			avgSpeed1 = (tickCounter1*avgSpeed1+speed)/(tickCounter1+1);
			tickCounter1++;
			avgSpeed2 = (tickCounter2*avgSpeed2+speed)/(tickCounter2+1);
			tickCounter2++;

			// Alternative: avgSpeed1=((n-1)*avgSpeed1+speed)/n

			/* Update Distance & time counters */
			distancecounter+= wheelCircumference;//<<2;
			while(distancecounter>MM_PER_DIST_UNIT) {
				distance1++;
				distance2++;
				distancecounter-=MM_PER_DIST_UNIT;
			}
			while(timecounter > TIMER1_TICS_PER_TIMEUNIT) {
				time1++;
				time2++;
				timecounter -= TIMER1_TICS_PER_TIMEUNIT;
			}
			
			/* write to display flag */ 
			updateData = TRUE;
			updatePower = TRUE;
			/* Shows if the calculations go to slow!*/
			if(calculateUpdate) {
				LCDCursor(0, 15);	
				WriteLCDData('!');
			}
		}
	} //END while(TRUE)
} //END main()

/*
 * Interrrupt routine
 * Saves the Captured timer1 value and handles timer1 overflows
 */
void interrupt CountAndCapture() {
	gie_off;	
	if(CCP1IF) {
		CCP1IF = 0;
		CCP1IE = 0;
		oldtime = lobyte(t0);
		// We'll throw the lower part away for actual speed calculations
		t0 = (tm1OverflowCounter<<8) +CCPR1H;
		tm1OverflowCounter = 0;
		calculateUpdate = TRUE; // Send Signal to calculate and show updates
		CCP1IE = 1;
	}
	if(TMR1IF) {
		TMR1IF = 0;
		if(tm1OverflowCounter==MIN_SPEED_OVERFLOW) {
			setSpeedToZero = TRUE; // Display 0,00 km/h 
			sleepCounter++;
			if(sleepCounter == SLEEP_COUNTER_MAX) {
				sleepNow=TRUE; // Goto sleep
			}
		} else {
			tm1OverflowCounter++;
			timecounter += 0xFFFF;
		}
	}
	gie_on;
}
